#pragma once

#include <map>
#include <cassert>

template < class T >
class Factory
{
	friend class CastedPtr;
	
	typedef int Index;
	typedef typename std::map < Index, T * > AllObjects;
	typedef typename AllObjects::iterator AllObjectsIterator;
	
public:
	template < class CAST_TO >
	class CastedPtr
	{
		friend class Factory < T >;
	public:
		CastedPtr ()
		: index ( 0 )
		, factory ( 0 )
		{
		}
		CastedPtr ( const CastedPtr & argPtr )
		: index ( argPtr.index )
		, factory ( argPtr.factory )
		{
		}
		bool IsNull ()
		{
			return ! factory || ! factory->GetObject ( index );
		}
		void Delete ()
		{
			if ( factory )
				factory->Delete ( index );
		}
		operator CastedPtr < T > ()
		{
			return CastTo < T > ();
		}
		CAST_TO * operator-> ()
		{
			assert ( ! IsNull () );
			return static_cast < CAST_TO * > ( factory->GetObject ( index ) );
		}
	private:
		CastedPtr ( Factory < T > * argFactory, typename Factory < T >::Index argIndex )
		: index ( argIndex )
		, factory ( argFactory )
		{
		}
		template < class U >
		CastedPtr < U > CastTo ()
		{
			return factory ? factory->CastTo < U > ( index ) : CastedPtr < U > ();
		}
	private:
		typename Factory < T >::Index index;
		Factory < T > * factory;
	};
	
	typedef CastedPtr < T > Ptr;
	
public:
	Factory ()
	: lastIndex ( 0 )
	{
	}
	~Factory ()
	{
		// IF THIS ASSERT FAILS, THEN SOME FACTORY INNOCENCE HAS NOT BEEN CHECKED AT CheckInnocence () STAGE
		assert ( allObjects.empty () );
	}
	template < class IMPLEMENTATION >
	CastedPtr < IMPLEMENTATION > CreateNewObject ()
	{
		return RegisterNewObject ( new IMPLEMENTATION () );
	}
	int ObjectsCount ()
	{ 
		return int ( allObjects.size () );
	}
private:
	template < class U >
	CastedPtr < U > CastTo ( Index index )
	{
		return CastedPtr < U > ( this, index );
	}
	template < class IMPLEMENTATION >
	CastedPtr < IMPLEMENTATION > RegisterNewObject ( IMPLEMENTATION * object )
	{
		allObjects [ ++lastIndex ] = object;
		return CastedPtr < IMPLEMENTATION > ( this, lastIndex );
	}
	void Delete ( Index index )
	{
		delete GetObject ( index );
		allObjects.erase ( allObjects.find ( index ) );
	}
	T * GetObject ( Index index )
	{
		AllObjectsIterator obj = allObjects.find ( index );
		if ( obj != allObjects.end () )
			return obj->second;
		else
			return 0;
	}
private:
	AllObjects allObjects;
	Index lastIndex;
};
